#ifndef zeus_REF_PTR_HPP_
#define zeus_REF_PTR_HPP_

namespace zeus
{
template<class T>
class ref_ptr
{
public:
    template<class U> static ref_ptr<T> static_cast_(ref_ptr<U> const &p)
    {
        return static_cast<T *>(p.get());
    }

    template<class U> static ref_ptr<T> const_cast_(ref_ptr<U> const &p)
    {
        return const_cast<T *>(p.get());
    }

    template<class U> static ref_ptr<T> dynamic_cast_(ref_ptr<U> const &p)
    {
        return dynamic_cast<T *>(p.get());
    }

private:
    typedef ref_ptr this_type;

public:
    typedef T element_type;

    ref_ptr() : _p(0)
    {

    }

    ref_ptr(T *p, bool add_ref = true) : _p(p)
    {
        if (_p != 0 && add_ref) ref_ptr_add_ref(_p);
    }

    ref_ptr(ref_ptr const &rhs) : _p(rhs._p)
    {
        if (_p != 0) ref_ptr_add_ref(_p);
    }

    template<typename U>
    ref_ptr(ref_ptr<U> const &rhs) : _p(rhs.get())
    {
        if (_p != 0) ref_ptr_add_ref(_p);
    }

    ~ref_ptr()
    {
        if (_p != 0) ref_ptr_release(_p);
    }

    ref_ptr & operator = (ref_ptr const &rhs)
    {
        this_type(rhs).swap(*this);
        return *this;
    }

    ref_ptr & operator = (T *rhs)
    {
        this_type(rhs).swap(*this);
        return *this;
    }

    template<typename U>
    ref_ptr & operator = (ref_ptr<U> const &rhs)
    {
        this_type(rhs).swap(*this);
        return *this;
    }

    T * get() const
    {
        return _p;
    }

    T & operator * () const
    {
        return *_p;
    }

    T * operator -> () const
    {
        return _p;
    }

    typedef T * this_type::*unspecified_bool_type;

    operator unspecified_bool_type () const
    {
        return _p == 0 ? 0 : &this_type::_p;
    }

    bool operator ! () const
    {
        return _p == 0;
    }

    void swap(ref_ptr &rhs)
    {
        T *tmp = _p;
        _p = rhs._p;
        rhs._p = tmp;
    }

private:
    T *_p;
};

template<class T, class U> inline bool operator == (ref_ptr<T> const &a, ref_ptr<U> const &b)
{
    return a.get() == b.get();
}

template<class T, class U> inline bool operator != (ref_ptr<T> const &a, ref_ptr<U> const &b)
{
    return a.get() != b.get();
}

template<class T> inline bool operator == (ref_ptr<T> const &a, T *b)
{
    return a.get() == b;
}

template<class T> inline bool operator != (ref_ptr<T> const &a, T *b)
{
    return a.get() != b;
}

template<class T> inline bool operator == (T *a, ref_ptr<T> const &b)
{
    return a == b.get();
}

template<class T> inline bool operator != (T *a, ref_ptr<T> const &b)
{
    return a != b.get();
}

template<class T> inline bool operator < (ref_ptr<T> const &a, ref_ptr<T> const &b)
{
    return std::less<T *>()(a.get(), b.get());
}

template<class T> void swap(ref_ptr<T> &lhs, ref_ptr<T> &rhs)
{
    lhs.swap(rhs);
}

template<class T> T * get_pointer(ref_ptr<T> const &p)
{
    return p.get();
}

template<class T, class U> ref_ptr<T> static_pointer_cast(ref_ptr<U> const &p)
{
    return static_cast<T *>(p.get());
}

template<class T, class U> ref_ptr<T> const_pointer_cast(ref_ptr<U> const &p)
{
    return const_cast<T *>(p.get());
}

template<class T, class U> ref_ptr<T> dynamic_pointer_cast(ref_ptr<U> const &p)
{
    return dynamic_cast<T *>(p.get());
}

class ref_count
{
private:
	int _ref_count;

public:
	ref_count() : _ref_count(0)
	{

	}

	virtual ~ref_count()
	{

	}

	ref_count(const ref_count &) : _ref_count(0)
	{

	}

	ref_count & operator = (const ref_count &);

	inline int inc_ref()
	{
		return ++_ref_count;
	}

	inline int dec_ref()
	{
		return --_ref_count;
	}

	inline bool unique()
	{
		return _ref_count == 1;
	}

	inline int get_ref_count()
	{
		return _ref_count;
	}
};

inline void ref_ptr_add_ref(ref_count *p)
{
	p->inc_ref();
}

inline void ref_ptr_release(ref_count *p)
{
	if (p->dec_ref() == 0)
	{
		delete p;
	}
}

class atomic_ref_count
{
private:
	int _ref_count;

public:
	atomic_ref_count() : _ref_count(0)
	{

	}

	virtual ~atomic_ref_count()
	{

	}

	atomic_ref_count(const atomic_ref_count &) : _ref_count(0)
	{

	}

	atomic_ref_count & operator = (const atomic_ref_count &);

	inline int inc_ref()
	{
		return __sync_add_and_fetch(&_ref_count, 1);
	}

	inline int dec_ref()
	{
		return __sync_sub_and_fetch(&_ref_count, 1);
	}

	inline bool unique()
	{
		return __sync_add_and_fetch(&_ref_count, 0) == 1;
	}

	inline int get_ref_count()
	{
		return _ref_count;
	}
};

inline void ref_ptr_add_ref(atomic_ref_count *p)
{
	p->inc_ref();
}

inline void ref_ptr_release(atomic_ref_count *p)
{
	if (p->dec_ref() == 0)
	{
		delete p;
	}
}
}

#endif /* REF_PTR_HPP_ */
